package org.femto.compmod.vis;

import org.femto.math.linalg.*;
import org.femto.math.Util;
import org.femto.math.vis.*;

import java.awt.event.MouseEvent;
import java.awt.Font;
import java.awt.geom.*;

import javax.media.opengl.*;
import com.sun.opengl.util.*;
import com.sun.opengl.util.j2d.*;

public class Camera extends Manipulable {

    public enum VIEW {
        XY,
        YZ,
        ZX,
        FIT,
        FITALL
    }

    private class View {
        private Vec3 eye = new Vec3();
        private Vec3 center = new Vec3();
        private Vec3 up = new Vec3();

        public View(Vec3 eye, Vec3 center, Vec3 up) {
            this.eye = eye;
            this.center = center;
            this.up = up;
        }

        public Vec3 getCenter() {
            return center;
        }

        public Vec3 getEye() {
            return eye;
        }

        public Vec3 getUp() {
            return up;
        }
    }

    private final View[] views = {
        new View(new Vec3(0,0,1), new Vec3(0,0,0), new Vec3(0,1,0)),
        new View(new Vec3(1,0,0), new Vec3(0,0,0), new Vec3(0,0,1)),
        new View(new Vec3(0,1,0), new Vec3(0,0,0), new Vec3(0,0,1))
    };

    public enum PROJECTION {
        ORTHOGRAPHIC,
        PERSPECTIVE
    }
    
    PROJECTION projection = PROJECTION.PERSPECTIVE;

    private Vec3 eye0 = new Vec3(5,5,5);
    private Vec3 center0 = new Vec3(1,0,1);
    private Vec3 up0 = new Vec3(0,0,1);
    private Vec3 forward0 = new Vec3();
    private Vec3 side0 = new Vec3();

    private Vec3 eye = eye0;
    private Vec3 center = center0;
    private Vec3 up = up0;
    private Vec3 forward = forward0;
    private Vec3 side = side0;

    private double fovY;
    private double nearZ;
    private double farZ;

    private Matrix4x4 modelview = new Matrix4x4();
    private Matrix4x4 R = new Matrix4x4();
    private Matrix4x4 dR = new Matrix4x4();
    private Matrix4x4 P = new Matrix4x4();
    private Matrix4x4 dP = new Matrix4x4();

    private int width;
    private int height;

    private double aspect;

    private Vec3 basisOriginColor = new Vec3(1,0,1);

    private VisualizerViewport viewport;
    private WorldRenderer renderer;

    ArcBall orbitBall = new ArcBall();
    Pan pan = new Pan();
    FieldOfView fov = new FieldOfView();

    // User interface-related
    private TextRenderer textRendererSmall = new TextRenderer(new Font("Arial", Font.PLAIN, 18));

    public Camera(VisualizerViewport viewport) {
        this.viewport = viewport;
        fovY = 45.0;
        nearZ = -100;
        farZ = 100;
        //dir = new Vec3(-1,-1,0);
        //dir.normalizeSelf();
        forward = Vec3.sub(center0, eye0).normalized();
        side = Vec3.cross(forward, up0).normalized();
        up = Vec3.cross(side, forward);

        computeModelview();

        addManipulator("ORBIT", orbitBall, MouseEvent.BUTTON1_MASK);
        addManipulator("PAN", pan,  MouseEvent.BUTTON1_MASK);
        addManipulator("FOV", fov,  MouseEvent.BUTTON1_MASK);

        setActiveManipulator("ORBIT");
    }

    public void init() {
    }

    public void setView(VIEW view) {
        if(view == VIEW.FIT) {

        }
        else if(view == VIEW.FITALL) {

        }
        else {
            final View v = views[view.ordinal()];
            this.eye0.set(v.getEye());
            this.center0.set(v.getCenter());
            this.up0.set(v.getUp());

            eye = eye0;
            center = center0;
            up = up0;

            recomputeEyeBasis();
            computeModelview();
            alignOrbitBall();
        }
    }

    public PROJECTION getProjection() {
        return projection;
    }

    public void setProjection(PROJECTION projection) {
        this.projection = projection;
    }

    public void setWorldRenderer(WorldRenderer renderer) {
        this.renderer = renderer;
    }
    
    public void reshape(int width, int height) {
        this.width = width;
        this.height = height;
        aspect = (double)width/(double)height;

        reshapeManipulators(width, height);
    }

    public void prepare(GL gl) {
        gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glDisable(GL.GL_LINE_SMOOTH);
        gl.glDisable(GL.GL_POINT_SMOOTH);
        gl.glDisable(GL.GL_POLYGON_SMOOTH);
        gl.glDisable(GL.GL_DITHER);
        gl.glDisable(GL.GL_CULL_FACE);

        gl.glClearColor(0.0f,0.0f,0.0f,0.0f);
    }

    public void drawGUI(GL gl) {
        String projectionText = projection.toString() + 
                (projection == PROJECTION.PERSPECTIVE ? String.format(" (fov=%.2f\u00B0)", fovY) : "");

        Rectangle2D projectionTextBounds = textRendererSmall.getBounds(projectionText);
        textRendererSmall.beginRendering(width, height);
        textRendererSmall.draw(projectionText, 0, height-(int)projectionTextBounds.getHeight());
        textRendererSmall.endRendering();
    }

    private Matrix4x4 getProjectionMatrix() {
        Matrix4x4 proj = new Matrix4x4();
        switch(projection) {
            case ORTHOGRAPHIC:
                proj.setOrthographicStandardAnamorphic(width, height, nearZ, farZ);
                break;
            case PERSPECTIVE:
                proj.setPerspective(fovY, aspect, 0.001, farZ);
                break;
        }
        return proj;
    }

    @Override
    protected void onActivateManipulator(String name) {
        if(name.equals("ORBIT")) {
            alignOrbitBall();
        }
    }

    @Override
    protected void onBeginDragging(String name) {
        if(name.equals("ORBIT")) {
            dR.setIdentity();
        }
        else if(name.equals("PAN")) {
            dP.setIdentity();
        }
    }

    @Override
    protected void onDragging(String name) {
        if(name.equals("ORBIT")) {
            dR = orbitBall.getTransform();

            Matrix4x4 Tcenter = new Matrix4x4();
            Matrix4x4 Tcenterinv = new Matrix4x4();

            Tcenter.setTranslation(center);
            Tcenterinv.setTranslation(center.negative());

            Matrix4x4 Rtmp = Matrix4x4.mul(dR, R);
            Matrix4x4 PTRTinv = Matrix4x4.mul(Tcenter, Rtmp, Tcenterinv);

            eye = PTRTinv.mul(eye0);
            up = Rtmp.mul(up0);

            recomputeEyeBasis();
        }
        else if(name.equals("PAN")) {
            dP = pan.getTransform();
            Matrix4x4 Pc = Matrix4x4.mul(dP, P);

            eye = Vec3.axpy(Pc.m[12], side, Vec3.axpy(Pc.m[13], up, eye0));
            center = Vec3.axpy(Pc.m[12], side, Vec3.axpy(Pc.m[13], up, center0));
        }
        else if(name.equals("FOV")) {
            final double foveps = 1;
            fovY = Util.clamp(fovY +fov.getTransform().m[0], foveps, 180-foveps);
        }
    }

    @Override
    protected void onEndDragging(String name) {
        if(name.equals("ORBIT")) {
            dR.setIdentity();
            eye0.set(eye);
            up0.set(up);
            alignOrbitBall();
        }
        else if(name.equals("PAN")) {
            dP.setIdentity();
            eye0.set(eye);
            center0.set(center);
        }
    }

    private void alignOrbitBall() {
        Matrix4x4 R0 = new Matrix4x4();
        R0.setBasis(modelview.getBasis(0), modelview.getBasis(1), modelview.getBasis(2));
        orbitBall.setR0(R0);
    }

    private void recomputeEyeBasis() {
        forward = Vec3.sub(center, eye).normalized();
        side = Vec3.cross(forward, up).normalized();
        up = Vec3.cross(side, forward).normalized();
    }

    private void computeModelview() {
        modelview.setLookatLocalBasis(side, up, forward, eye);

        //System.out.println(Vec3.dist(center, eyeT));
        //M.setLookatLocalBasis(side, up, forward, eye);
    }

    public void render(GL gl) {
        gl.glClear(GL.GL_DEPTH_BUFFER_BIT);

        computeModelview();

        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glPushMatrix();
        gl.glLoadMatrixd(getProjectionMatrix().m, 0);

        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPushMatrix();
        gl.glLoadMatrixd(modelview.m, 0);

        gl.glColor3d(0.3, 0.3, 0.3);
        Drawing.drawInfiniteGroundGrid(gl);

        gl.glColor3d(1.0, 1.0, 1.0);
        renderer.render(gl);

        gl.glPushMatrix();
        gl.glTranslated(center.v[0], center.v[1], center.v[2]);
        Drawing.drawBasis(gl);
        Drawing.drawScreenBox(gl, 5, basisOriginColor);

        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPopMatrix();

        Drawing.drawBasis(gl);
        Drawing.drawScreenBox(gl, 5, basisOriginColor);

        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glPopMatrix();
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glPopMatrix();
    }


}
